package xin.petstore.framework.web.sitemesh;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.sitemesh.builder.SiteMeshFilterBuilder;
import org.sitemesh.config.ObjectFactory;
import org.sitemesh.config.properties.PropertiesFilterConfigurator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

public class RemoteConfigurableSiteMeshFilter
        implements Filter {
    private static final Logger logger = Logger.getLogger(RemoteConfigurableSiteMeshFilter.class.getName());
    private boolean initialized;
    private File xmlConfigFile;
    private FilterConfig filterConfig;
    private Map<String, String> configProperties;
    private boolean autoReload;
    private volatile Filter filter;
    private volatile long timestampOfXmlFileAtLastLoad;
    private final Object configLock = new Object();
    public static final String CONFIG_FILE_PARAM = "configFile";
    public static final String CONFIG_FILE_DEFAULT = "/sitemesh3.xml";
    public static final String AUTO_RELOAD_PARAM = "autoReload";
    public static final boolean AUTO_RELOAD_DEFAULT = true;

    public void init(FilterConfig filterConfig)
            throws ServletException {
        this.filterConfig = filterConfig;
        this.configProperties = getConfigProperties(filterConfig);
        this.autoReload = getAutoReload();
        logger.config("Auto reloading " + (this.autoReload ? "enabled" : "disabled"));

        synchronized (this.configLock) {
            deployNewFilter(setup());
        }
        this.initialized = true;
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (!this.initialized) {
            throw new ServletException(getClass().getName() + ".init(FilterConfig) was not called");
        }

        this.filter.doFilter(servletRequest, servletResponse, filterChain);
    }

    public void destroy() {
        synchronized (this.configLock) {
            if (this.filter != null)
                this.filter.destroy();
        }
    }

    protected Map<String, String> getConfigProperties(FilterConfig filterConfig) {
        Map initParams = new HashMap();
        for (Enumeration initParameterNames = filterConfig.getInitParameterNames(); initParameterNames.hasMoreElements(); ) {
            String key = (String) initParameterNames.nextElement();
            String value = filterConfig.getInitParameter(key).trim();
            initParams.put(key, value);
        }
        return initParams;
    }

    protected Filter setup() throws ServletException {
        ObjectFactory objectFactory = getObjectFactory();
        SiteMeshFilterBuilder builder = new SiteMeshFilterBuilder();

        new PropertiesFilterConfigurator(objectFactory, this.configProperties)
                .configureFilter(builder);
        ServletContext servletContext = this.filterConfig.getServletContext();

        new XmlFilterConfigurator(getObjectFactory(),
                loadConfigXml(getConfigFileName()))
                .configureFilter(builder, servletContext.getRealPath("/"));

        return builder.create();
    }

    protected boolean reloadRequired() {
        return this.timestampOfXmlFileAtLastLoad != this.xmlConfigFile.lastModified();
    }

    protected boolean getAutoReload() {
        String autoReload = (String) this.configProperties.get("autoReload");
        if (autoReload == null) {
            return true;
        }
        String lower = autoReload.toLowerCase();
        return (lower.equals("1")) || (lower.equals("true")) || (lower.equals("yes"));
    }

    protected String getConfigFileName() {
        String config = (String) this.configProperties.get("configFile");
        if ((config == null) || (config.length() == 0)) {
            config = "/sitemesh3.xml";
        }
        return config;
    }

    protected ObjectFactory getObjectFactory() {
        return new ObjectFactory.Default();
    }

    protected Element loadConfigXml(FilterConfig filterConfig, String configFilePath)
            throws ServletException {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = factory.newDocumentBuilder();

            this.xmlConfigFile = new File(configFilePath);

            ServletContext servletContext = filterConfig.getServletContext();

            if (servletContext.getRealPath(configFilePath) != null) {
                this.xmlConfigFile = new File(servletContext.getRealPath(configFilePath));
            }

            if (this.xmlConfigFile.canRead()) {
                try {
                    this.timestampOfXmlFileAtLastLoad = this.xmlConfigFile.lastModified();
                    logger.config("Loading SiteMesh 3 config file: " + this.xmlConfigFile.getAbsolutePath());
                    Document document = documentBuilder.parse(this.xmlConfigFile);
                    return document.getDocumentElement();
                } catch (SAXException e) {
                    throw new ServletException("Could not parse " + this.xmlConfigFile.getAbsolutePath(), e);
                }
            }
            InputStream stream = servletContext.getResourceAsStream(configFilePath);
            if (stream == null) {
                logger.config("No config file present - using defaults and init-params. Tried: " +
                        this.xmlConfigFile.getAbsolutePath() + " and ServletContext:" + configFilePath);
                return null;
            }
            try {
                logger.config("Loading SiteMesh 3 config file from ServletContext " + configFilePath);
                Document document = documentBuilder.parse(stream);
                Element localElement = document.getDocumentElement();
                return localElement;
            } catch (SAXException e) {
                throw new ServletException("Could not parse " + configFilePath + " (loaded by ServletContext)", e);
            } finally {
                stream.close();
            }
        } catch (IOException e) {
            throw new ServletException(e);
        } catch (ParserConfigurationException e) {
            throw new ServletException("Could not initialize DOM parser", e);
        }

    }

    protected void reloadIfNecessary()
            throws ServletException {
        if ((this.autoReload) && (reloadRequired())) {
            synchronized (this.configLock) {
                if (reloadRequired()) {
                    deployNewFilter(setup());
                }
            }
        }
    }

    protected void deployNewFilter(Filter newFilter)
            throws ServletException {
        Filter oldFilter = this.filter;
        if (newFilter == null) {
            throw new ServletException("Cannot deploy null filter");
        }
        newFilter.init(this.filterConfig);
        this.filter = newFilter;
        if (oldFilter != null) {
            oldFilter.destroy();
        }
    }

    protected Element loadConfigXml(String configFilePath) throws ServletException {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = factory.newDocumentBuilder();

            InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(configFilePath);
            if (stream == null) {
                logger.config("No config file:" + configFilePath);
                return null;
            }
            try {
                logger.config("Loading SiteMesh 3 config file from ServletContext " + configFilePath);
                Document document = documentBuilder.parse(stream);
                Element localElement = document.getDocumentElement();
                return localElement;
            } catch (SAXException e) {
                throw new ServletException("Could not parse " + configFilePath + " (loaded by ServletContext)", e);
            } finally {
                stream.close();
            }
        } catch (IOException e) {
            throw new ServletException(e);
        } catch (ParserConfigurationException e) {
            throw new ServletException("Could not initialize DOM parser", e);
        }

    }
}

